using UnityEngine;

/// <summary>
/// CPU实现的Frustum culling.
/// 用于快速验证算法正确性.
/// Inside时白色; outside时红色.
/// </summary>
public class CpuFrustumChecker : MonoBehaviour
{
    /// <summary>
    /// 检查方案
    /// </summary>
    public enum CheckType
    {
        /// <summary>
        /// 自定义方案. 和ComputeShader算法相同.
        /// </summary>
        Custom,

        /// <summary>
        /// Unity.GeometryUtility方案.官方的实现.
        /// </summary>
        GeometryUtility,
    }

    public CheckType CurType = CheckType.Custom;

    private Renderer debugRenderer;

    public Renderer DebugRenderer
    {
        get
        {
            if (debugRenderer == null)
            {
                debugRenderer = GetComponent<Renderer>();
            }
            return debugRenderer;
        }
    }

    private Camera curCamera;

    private void Awake()
    {
        curCamera = Camera.main;
        transform.localScale = Vector3.one;
    }

    private void LateUpdate()
    {
        bool inside = false;
        switch (CurType)
        {
            case CheckType.Custom:
                {
                    inside = CheckInsideCustom();
                }
                break;

            case CheckType.GeometryUtility:
                {
                    inside = CheckInsideGeometryUtility();
                }
                break;
        }
        DebugRenderer.material.color = inside ? Color.white : Color.red;
    }

    /// <summary>
    /// 自定义方案检查是否Inside
    /// </summary>
    /// <returns>true:Frusutum之内;false:之外</returns>
    private bool CheckInsideCustom()
    {
        bool inside = true;
        var planes = curCamera.GetFrustumPlane();
        foreach (var plane in planes)
        {
            if (IsOutsideThePlane(plane, transform.position))
            {
                //在某个平面的之外, 则一定在Frustum之外.
                inside = false;
                return inside;
            }
        }

        return inside;
    }

    /// <summary>
    /// 在平面外(平面的背面)
    /// </summary>
    /// <param name="plane"></param>
    /// <param name="position"></param>
    /// <returns></returns>
    public bool IsOutsideThePlane(Vector4 plane, Vector3 position)
    {
        Vector3 planeNormal = new Vector3(plane.x, plane.y, plane.z);
        if (Vector3.Dot(planeNormal, position) + plane.w > 0)
        {
            return true;
        }
        return false;
    }

    /// <summary>
    /// 使用GeometryUtility判断内外
    /// </summary>
    /// <returns>true:Frusutum之内;false:之外</returns>
    private bool CheckInsideGeometryUtility()
    {
        var bound = new Bounds();
        bound.center = transform.position;
        bound.size = Vector3.one;

        var planes = GeometryUtility.CalculateFrustumPlanes(curCamera);
        return GeometryUtility.TestPlanesAABB(planes, bound);
    }

    [ContextMenu("比较两种Plane的表示方式")]
    private void ComparePlanes()
    {
        /*
        var plane = Helper.GetPlane(Vector3.up, Vector3.zero);
        var geoPlane = new Plane(Vector3.up, 0f);
        var v4GeoPlane = geoPlane.ToVector4();
        */

        var planes = curCamera.GetFrustumPlane();
        var geoPlanes = GeometryUtility.CalculateFrustumPlanes(curCamera);
    }
}